/**
 * @file RequestUtils.cpp
 * @brief 请求工具类实现，提供处理请求中文件内容的功能
 * @author CloudLinker
 * @date 2023-11-20
 */

#include "CLink/Utils/RequestUtils.h"
#include <Poco/Net/HTMLForm.h>
#include <Poco/Net/MessageHeader.h>
#include <Poco/Net/PartSource.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/StreamCopier.h>
#include <sstream>
#include <algorithm>

using namespace CLink::Utils;

std::vector<RequestUtils::FileContent> RequestUtils::handleMultipartFiles(
    const Poco::Net::HTTPRequest& request,
    const std::string& boundary)
{
    FilePartHandler handler;
    Poco::Net::HTMLForm form(request, dynamic_cast<Poco::Net::HTTPServerRequest&>(const_cast<Poco::Net::HTTPRequest&>(request)).stream(), handler);
    return handler.getFiles();
}

std::optional<RequestUtils::FileContent> RequestUtils::extractFile(
    const Poco::Net::HTTPRequest& request,
    const std::string& paramName)
{
    const std::string contentType = request.getContentType();
    if (contentType.find("multipart/form-data") == std::string::npos)
    {
        return std::nullopt;
    }

    std::string boundary;
    size_t pos = contentType.find("boundary=");
    if (pos != std::string::npos)
    {
        boundary = contentType.substr(pos + 9); // 9 is length of "boundary="
    }

    auto files = handleMultipartFiles(request, boundary);
    auto it = std::find_if(files.begin(), files.end(),
        [&paramName](const FileContent& file) {
            return file.name == paramName;
        });

    if (it != files.end())
    {
        return *it;
    }
    return std::nullopt;
}

std::map<std::string, RequestUtils::FileContent> RequestUtils::extractAllFiles(
    const Poco::Net::HTTPRequest& request)
{
    std::map<std::string, FileContent> result;
    const std::string contentType = request.getContentType();
    if (contentType.find("multipart/form-data") == std::string::npos)
    {
        return result;
    }

    std::string boundary;
    size_t pos = contentType.find("boundary=");
    if (pos != std::string::npos)
    {
        boundary = contentType.substr(pos + 9); // 9 is length of "boundary="
    }

    auto files = handleMultipartFiles(request, boundary);
    for (const auto& file : files)
    {
        result.emplace(file.name, file);
    }

    return result;
}

bool RequestUtils::hasFiles(const Poco::Net::HTTPRequest& request)
{
    const std::string contentType = request.getContentType();
    if (contentType.find("multipart/form-data") == std::string::npos)
    {
        return false;
    }

    std::string boundary;
    size_t pos = contentType.find("boundary=");
    if (pos != std::string::npos)
    {
        boundary = contentType.substr(pos + 9); // 9 is length of "boundary="
    }

    auto files = handleMultipartFiles(request, boundary);
    return !files.empty();
}

void RequestUtils::FilePartHandler::handlePart(
    const Poco::Net::MessageHeader& header,
    std::istream& stream)
{
    std::string disposition;
    if (header.has("Content-Disposition"))
    {
        disposition = header.get("Content-Disposition");
    }

    if (disposition.find("filename=") != std::string::npos)
    {
        FileContent file;
        
        // Extract filename
        size_t start = disposition.find("filename=\"") + 10;
        size_t end = disposition.find("\"", start);
        file.name = disposition.substr(start, end - start);

        // Get content type
        if (header.has("Content-Type"))
        {
            file.contentType = header.get("Content-Type");
        }

        // Read file data
        std::ostringstream oss;
        Poco::StreamCopier::copyStream(stream, oss);
        const std::string& data = oss.str();
        file.data.assign(data.begin(), data.end());

        files_.push_back(std::move(file));
    }
}